﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories.SearchExtensions;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface ITransportadoraRepository : IUsuariosHolderRepository<Transportadora, StatisticsBase>
    {
        int GetCount();
        IEnumerable<Transportadora> GetModels(string nome, string cidade, string bairro, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1);
        int[] FilterSynchronizedTransportadoras(int[] transportadoras);
    }

    public class TransportadoraRepository : UsuariosHolderRepository<Transportadora, StatisticsBase, TransportadoraError>, ITransportadoraRepository
    {
        public TransportadoraRepository(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        protected override IEnumerable<Expression<Func<Transportadora, bool>>> DoGetUniquenessExpressions(Transportadora model)
        {
            yield return x => x.Nome == model.Nome;
        }

        protected override TransportadoraError RepresentanteMissmatchError
        {
            get { return TransportadoraError.RepresentanteMissmatchError; }
        }

        protected override StatisticsBase GetStatistics(int modelCount)
        {
            return new StatisticsBase(modelCount);
        }

        protected override TransportadoraError DuplicatedError { get { return TransportadoraError.DuplicatedNome; } }

        public int GetCount()
        {
            return Session.QueryOver<Transportadora>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .RowCount();
        }

        public IEnumerable<Transportadora> GetModels(string nome, string cidade, string bairro, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1)
        {
            const string TableAlias = "tr";
            
            ICriteria criteria = CreateCriteria(cidade, bairro, attributeMap, TableAlias);

            if (!String.IsNullOrEmpty(nome))
            {
                string likeNome = "%" + nome + "%";
                criteria = criteria.Add(Restrictions.InsensitiveLike(Projections.Property<Transportadora>(x => x.Nome), likeNome));
            }

            IEnumerable<Transportadora> transportadoraFiltro = User.Filtros.OfType<Transportadora>();
            if (transportadoraFiltro.Any())
            {
                criteria.Add(Restrictions.In(Projections.Property<Transportadora>(x => x.Id), transportadoraFiltro.Select(x => x.Id).ToArray()));
            }

            if (searchExtensions != null)
            {
                foreach (ISearchExtension searchExtension in searchExtensions)
                {
                    criteria = searchExtension.Execute(criteria, TableAlias);
                }
            }

            criteria = criteria.AddOrder(Order.Asc(Projections.Property<Transportadora>(x => x.Nome)));

            int modelCount;
            IEnumerable<Transportadora> transportadoras = FuturePaging(criteria, page, out modelCount);
            SetProperties(transportadoras);
            statistics = GetStatistics(modelCount);
            return transportadoras;
        }

        protected override IQueryOver<Transportadora, Transportadora> DoGetModelsQueryOver(IQueryOver<Transportadora, Transportadora> queryOver)
        {
            return queryOver.OrderBy(x => x.Nome).Asc;
        }

        public override Dictionary<Guid, string> GetModelNames()
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, Nome
                FROM Transportadora
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId
                ORDER BY Nome")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("Nome", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }

        public override Dictionary<Guid, string> GetModelNames(string search)
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, Nome
                FROM Transportadora
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId AND Nome LIKE :Search
                ORDER BY Nome")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("Nome", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .SetParameter("Search", "%" + search + "%")
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }

        public int[] FilterSynchronizedTransportadoras(int[] transportadoras)
        {
            EnsureIsRepresentante();

            DetachedCriteria detachedCriteria = DetachedCriteria.For<Transportadora>()
                .SetProjection(Projections.Property<Transportadora>(x => x.Id))
                .Add(Restrictions.EqProperty("tj.Id", Projections.Property<Transportadora>(x => x.Id)))
                .Add(Restrictions.Eq(Projections.Property<Transportadora>(x => x.Representante.Id), GetRepresentanteId()));
            IEnumerable<int> synchronizedTransportadoras = Session.CreateCriteria<TransportadoraJolitex>("tj")
                .SetProjection(Projections.Property<TransportadoraJolitex>(x => x.Codigo))
                .Add(Restrictions.In(Projections.Property<TransportadoraJolitex>(x => x.Codigo), transportadoras.ToArray()))
                .Add(Subqueries.Exists(detachedCriteria))
                .Future<int>()
                .Distinct();
            return transportadoras.Except(synchronizedTransportadoras).ToArray();
        }
    }

    public enum TransportadoraError
    {
        DuplicatedNome,
        RepresentanteMissmatchError
    }
}
